package tw.com.hitevision.whiteboard.android.widgets.MeasurementTools;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.text.TextPaint;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;

import java.util.Locale;

import tw.com.hitevision.whiteboard.android.R;
import tw.com.hitevision.whiteboard.android.util.TextLayoutUtil;
import tw.com.hitevision.whiteboard.android.widgets.OptionsRulerMenuAdapter;

import static tw.com.hitevision.whiteboard.android.widgets.MeasurementTools.MeasurementValues.PROTRACTOR_SKIP;
import static tw.com.hitevision.whiteboard.android.widgets.MeasurementTools.MeasurementValues.PROTRACTOR_UNIT;
import static tw.com.hitevision.whiteboard.android.widgets.MeasurementTools.MeasurementValues.ROTATE_SKIP;
import static tw.com.hitevision.whiteboard.android.widgets.MeasurementTools.MeasurementValues.ROTATE_UNIT;
import static tw.com.hitevision.whiteboard.android.widgets.OptionsRulerMenuAdapter.PROTRACTOR_DEGREE_180;
import static tw.com.hitevision.whiteboard.android.widgets.OptionsRulerMenuAdapter.PROTRACTOR_DEGREE_360;

public class Protractor extends BasicRuler {
    private Drawable protractorDrawable;
    private Drawable closeBtnDrawable;
    private Drawable rotateBtnDrawable;
    private final float defaultDiameter;
    private final float maxDiameter;
    private final float minDiameter;
    private float radius;
    private boolean reset = true;
    private boolean update = false;
    private boolean updateDetectLineRegion = false;
    private PointF center = new PointF();
    private PointF start;
    private PointF end;
    private Path detectPath = new Path();
    private Region detectRegion = new Region();
    private float canvasDegree = 0f;
    private float totalDegree = 0f;
    private float strokeLineWidth;
    private MeasurementAction measurementAction;
    private float dragX;
    private float dragY;
    private float dragX2;
    private float dragY2;
    private float translateX;
    private float translateY;
    private float buttonSize;
    private float protractorBottomHeight;
    private float closeBtnRadius;
    private float closeBtnDegree;
    private PointF closeBtnP = new PointF();
    private PointF rotateBtnP = new PointF();
    private float bottomBarHeight;
    private float scaleRatio = 1f;
    private float realScaleRatio = 1f;
    private TextPaint measurementRectTextPaint;
    private float measurementRectWidth;
    private float measurementRectHeight;
    private float measurementRectRadius;
    private Paint measurementRectPaint;
    private Paint measurementRectStrokePaint;
    private float measurementTopMargin;
    private PointF lineStartP = new PointF();
    private float startAngle;
    private float endAngle;
    private float indicatorSize;
    private MeasurementToolListener listener;
    private Drawable arcModeDrawable;
    private Drawable angleModeDrawable;
    private Drawable lineModeDrawable;
    private Drawable solidModeDrawable;
    private PointF modeP = new PointF();
    private ProtractorPaintMode paintMode = ProtractorPaintMode.ARC;
    private float modeMargin;
    private ProtractorMode protractorMode;
    private Paint drawLinePaint;
    private Path drawLinePath;
    private Path detectLinePath;
    private Region detectLineRegion;
    private PointF motionCenterP = new PointF();
    private String measurementText = "";

    private Paint debugPaint;

    public Protractor(Context context, ProtractorMode protractorMode, MeasurementToolListener listener) {
        super(context);
        name = protractorMode == ProtractorMode.DEGREE180 ? PROTRACTOR_DEGREE_180 : PROTRACTOR_DEGREE_360;
        this.listener = listener;

        this.protractorMode = protractorMode;
        protractorDrawable = protractorMode == ProtractorMode.DEGREE180 ?
                getResources().getDrawable(R.drawable.img_protractor_180) :
                getResources().getDrawable(R.drawable.img_protractor_full);
        closeBtnDrawable = getResources().getDrawable(R.drawable.ic_ruler_close);
        rotateBtnDrawable = getResources().getDrawable(R.drawable.ic_ruler_rotate);
        defaultDiameter = getResources().getDimensionPixelOffset(R.dimen.ruler_protractor_diameter_default);
        maxDiameter = getResources().getDimensionPixelOffset(R.dimen.ruler_protractor_diameter_max);
        minDiameter = getResources().getDimensionPixelOffset(R.dimen.ruler_protractor_diameter_min);
        buttonSize = getResources().getDimensionPixelOffset(R.dimen.ruler_btn_size);
        protractorBottomHeight = getResources().getDimensionPixelOffset(R.dimen.ruler_protractor_bottom_bar_height);
        closeBtnRadius = protractorMode == ProtractorMode.DEGREE180 ?
                getResources().getDimension(R.dimen.ruler_protractor_close_button_radius) :
                getResources().getDimensionPixelOffset(R.dimen.ruler_protractor_360_button_radius);
        closeBtnDegree = getResources().getInteger(R.integer.ruler_protractor_close_btn_degree);
        bottomBarHeight = getResources().getDimensionPixelOffset(R.dimen.bottom_bar_height);
        measurementRectTextPaint = new TextPaint();
        measurementRectTextPaint.setAntiAlias(true);
        measurementRectTextPaint.setColor(getResources().getColor(R.color.hitevision_blue));
        measurementRectTextPaint.setTextSize(getResources().getDimensionPixelOffset(R.dimen.ruler_text_font_size));
        measurementRectWidth = getResources().getDimensionPixelOffset(R.dimen.ruler_protractor_measurement_width);
        measurementRectHeight = getResources().getDimensionPixelOffset(R.dimen.ruler_protractor_measurement_height);
        measurementRectRadius = getResources().getDimensionPixelOffset(R.dimen.ruler_protractor_measurement_radius);
        measurementRectPaint = initStrokePaint();
        measurementRectPaint.setStyle(Paint.Style.FILL);
        measurementRectPaint.setColor(Color.WHITE);
        measurementRectStrokePaint = initStrokePaint();
        measurementRectStrokePaint.setStyle(Paint.Style.STROKE);
        measurementRectStrokePaint.setColor(getResources().getColor(R.color.ruler_protractor_stroke_color));
        measurementRectStrokePaint.setStrokeWidth(getResources().getDimensionPixelSize(R.dimen.ruler_protractor_stroke_width));
        measurementTopMargin = getResources().getDimensionPixelOffset(R.dimen.ruler_protractor_measurement_top_margin)
                + measurementRectHeight / 2f;
        indicatorSize = getResources().getDimensionPixelSize(R.dimen.ruler_protractor_indicator_size);
        arcModeDrawable = getResources().getDrawable(R.drawable.ic_protractor_arc);
        angleModeDrawable = getResources().getDrawable(R.drawable.ic_protractor_angle);
        lineModeDrawable = getResources().getDrawable(R.drawable.ic_protractor_sector_line);
        solidModeDrawable = getResources().getDrawable(R.drawable.ic_protractor_sector_solid);
        modeMargin = getResources().getDimensionPixelOffset(R.dimen.ruler_protractor_mode_margin);
        // init detect line
        @ColorInt int detectColor = context.getResources().getColor(R.color.ruler_detect_stroke_color);
        float detectLineStrokeWidth = context.getResources().getDimensionPixelOffset(R.dimen.ruler_detect_range);
        drawLinePaint = initStrokePaint();
        drawLinePaint.setColor(detectColor);
        drawLinePaint.setStrokeWidth(detectLineStrokeWidth);
        drawLinePath = new Path();
        detectLinePath = new Path();
        detectLineRegion = new Region();

        // debug
        debugPaint = initStrokePaint();
        debugPaint.setColor(getResources().getColor(R.color.pen_red));
        debugPaint.setStrokeWidth(2);
    }

    @Override
    public void loadMeasurementToolRecord(MeasurementToolRecord record) {
        reset = false;
        initProtractor();

        totalDegree = record.getRotateDegree();
        scaleRatio = record.getScaleRatio();
        realScaleRatio = record.getScaleRatio();
        center.set(record.getCenterF());
        motionCenterP.set(center);
        start = new PointF(center.x + radius, center.y);
        end = new PointF(center.x - radius, center.y);

        float[] points = new float[]{start.x, start.y, end.x, end.y};
        Matrix matrix = new Matrix();
        matrix.setRotate(totalDegree, center.x, center.y);
        matrix.mapPoints(points);
        matrix.setScale(scaleRatio, scaleRatio, center.x, center.y);
        matrix.mapPoints(points);

        start.x = points[0];
        start.y = points[1];
        end.x = points[2];
        end.y = points[3];

        radius = PointF.length(start.x - end.x, start.y - end.y) / 2f;
        if (protractorMode == ProtractorMode.DEGREE180) {
            closeBtnRadius *= scaleRatio;
        }

        if (record instanceof ProtractorRecord) {
            paintMode = ((ProtractorRecord) record).getMode();
        }

        updateDetectRegion();
        update = true;
        invalidate();
    }

    @Override
    public MeasurementToolRecord createMeasurementToolRecord() {
        return new ProtractorRecord(name, center.x, center.y, totalDegree, scaleRatio, paintMode);
    }

    public enum ProtractorMode {
        DEGREE180, DEGREE360
    }

    public void setDrawingLineWidth(float width) {
        this.strokeLineWidth = width;
    }

    public boolean rulerOnTouch(View v, MotionEvent event, boolean canDraw) {
        int action = event.getActionMasked();
        float x = event.getX();
        float y = event.getY();
        int intX = (int) event.getX();
        int intY = (int) event.getY();

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                boolean hitRuler = detectRegion.contains(intX, intY);
                boolean hitDetectionRegion = canDraw && detectLineRegion.contains((int) x, (int) y);
                boolean hitMode = PointF.length(x - modeP.x, y - modeP.y) <= indicatorSize / 2f;
                boolean contain = hitRuler || hitDetectionRegion;
                measurementAction = MeasurementAction.NONE;

                if (contain) {
                    dragX = x;
                    dragY = y;

                    if (hitMode) {
                        measurementAction = MeasurementAction.NONE;
                    } else if (hitDetectionRegion) {
                        PointF v1 = new PointF(start.x - center.x, start.y - center.y);
                        PointF v2 = new PointF(x - center.x, y - center.y);
                        float arc1 = (float) Math.atan2(v1.x, v1.y);
                        float arc2 = (float) Math.atan2(v2.x, v2.y);
                        float d = (float) Math.toDegrees(arc2 - arc1);
                        d = (d + 360) % 360;
                        d += getDegreeStopOffset(d, PROTRACTOR_SKIP, PROTRACTOR_UNIT);
                        float protractorLimit = protractorMode == ProtractorMode.DEGREE180 ? 180 : 360;
                        d = Math.max(0, Math.min(protractorLimit, d));
                        float[] points = new float[]{start.x, start.y};
                        Matrix matrix = new Matrix();
                        matrix.setRotate(-d, center.x, center.y);
                        matrix.mapPoints(points);
                        lineStartP = new PointF(points[0], points[1]);
                        startAngle = d;
                        endAngle = d;
                        measurementAction = MeasurementAction.DRAW;
                    } else {
                        if (PointF.length(x - closeBtnP.x, y - closeBtnP.y) <= buttonSize / 2f) {
                            measurementAction = MeasurementAction.CLOSE;
                        } else if (PointF.length(x - rotateBtnP.x, y - rotateBtnP.y) <= buttonSize / 2f) {
                            measurementAction = MeasurementAction.ROTATE;
                        } else {
                            measurementAction = MeasurementAction.MOVE;
                        }
                    }
                }

                return contain;
            }
            case MotionEvent.ACTION_POINTER_DOWN: {
                if (event.getPointerCount() <= 2 && measurementAction == MeasurementAction.MOVE) {
                    measurementAction = MeasurementAction.MULTI_FINGER;
                    dragX2 = event.getX(event.getActionIndex());
                    dragY2 = event.getY(event.getActionIndex());
                }
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                switch (measurementAction) {
                    case DRAW: {
                        drawProtractorLine(x, dragX, y, dragY);
                        if (listener != null) {
                            float degree = endAngle - startAngle;
                            degree += getDegreeStopOffset(degree, PROTRACTOR_SKIP, PROTRACTOR_UNIT);
                            listener.onDrawingProtractor(center.x, center.y, lineStartP.x, lineStartP.y, -degree, false, paintMode.ordinal());
                        }
                        break;
                    }
                    case MOVE: {
                        move(x - dragX, y - dragY);
                        break;
                    }
                    case ROTATE: {
                        motionCenterP.set(center.x, center.y);
                        rotate(x, dragX, y, dragY, center.x, center.x, center.y, center.y);
                        break;
                    }
                    case MULTI_FINGER: {
                        if (event.getPointerCount() >= 2) {
                            float x1 = event.getX(0);
                            float y1 = event.getY(0);
                            float x2 = event.getX(1);
                            float y2 = event.getY(1);
                            scale(dragX, dragY, dragX2, dragY2, x1, y1, x2, y2);
                            dragX2 = x2;
                            dragY2 = y2;
                        } else {
                            measurementAction = MeasurementAction.MOVE;
                        }
                        break;
                    }
                }
                dragX = x;
                dragY = y;
                measurementText = getMeasurementText(measurementAction);

                return false;
            }
            case MotionEvent.ACTION_UP: {
                switch (measurementAction) {
                    case DRAW: {
                        drawProtractorLine(x, dragX, y, dragY);
                        if (listener != null) {
                            float degree = endAngle - startAngle;
                            degree += getDegreeStopOffset(degree, PROTRACTOR_SKIP, PROTRACTOR_UNIT);
                            listener.onDrawingProtractor(center.x, center.y, lineStartP.x, lineStartP.y, -degree, true, paintMode.ordinal());
                            startAngle = 0;
                            endAngle = 0;
                            update = true;
                            invalidate();
                        }
                        break;
                    }
                    case CLOSE: {
                        close();
                        break;
                    }
                    case MULTI_FINGER:
                    case MOVE: {
//                        afterMove(x - dragX, y - dragY);
                        afterMove(0, 0);
                        afterRotate(dragX, dragX, dragY, dragY, dragX2, dragX2, dragY2, dragY2);
                        break;
                    }
                    case ROTATE: {
                        afterRotate(x, dragX, y, dragY, center.x, center.x, center.y, center.y);
                        break;
                    }
                    case NONE: {
                        boolean hitMode = PointF.length(x - modeP.x, y - modeP.y) <= indicatorSize / 2f;
                        if (hitMode) {
                            switchMode();
                        }
                        break;
                    }
                }
                measurementText = "";
                if (listener != null) {
                    listener.updateRecord(name);
                }
                return false;
            }
        }

        return false;
    }

    private void close() {
        OptionsRulerMenuAdapter.getLister(name).onClick(this);
        if (listener != null) {
            listener.onClose(name);
        }
    }

    private void move(float x, float y) {
        PointF nv = getNormalVectorCCW(getUnitVector(end.x - start.x, end.y - start.y));
        if (y + center.y + nv.y * radius / 2f + translateY > getHeight() - bottomBarHeight) {
            y = getHeight() - bottomBarHeight - center.y - nv.y * radius / 2f - translateY;
        }
        translateX += x;
        translateY += y;
        update = true;
        invalidate();
    }

    private void afterMove(float x, float y) {
        move(x, y);
        center.x += translateX;
        center.y += translateY;
        start.x += translateX;
        start.y += translateY;
        end.x += translateX;
        end.y += translateY;
        translateX = 0;
        translateY = 0;
        updateDetectRegion();
        update = true;
        invalidate();
    }

    private void rotate(float x, float dragX, float y, float dragY, float x2, float dragX2, float y2, float dragY2) {
        PointF v1 = new PointF(x - x2, y - y2);
        PointF v2 = new PointF(dragX - dragX2, dragY - dragY2);
        float arc1 = (float) Math.atan2(v1.x, v1.y);
        float arc2 = (float) Math.atan2(v2.x, v2.y);
        float d = (float) Math.toDegrees(arc2 - arc1);
        canvasDegree += d;

        update = true;
        invalidate();
    }

    private void afterRotate(float x, float dragX, float y, float dragY, float x2, float dragX2, float y2, float dragY2) {
        PointF v1 = new PointF(x - x2, y - y2);
        PointF v2 = new PointF(dragX - dragX2, dragY - dragY2);
        float arc1 = (float) Math.atan2(v1.x, v1.y);
        float arc2 = (float) Math.atan2(v2.x, v2.y);
        float d = (float) Math.toDegrees(arc2 - arc1);
        canvasDegree += d;

        canvasDegree += getDegreeStopOffset(totalDegree + canvasDegree, ROTATE_SKIP, ROTATE_UNIT);

        Matrix matrix = new Matrix();
        float[] p = new float[]{start.x, start.y, end.x, end.y};
        matrix.setRotate(canvasDegree, motionCenterP.x, motionCenterP.y);
        matrix.mapPoints(p);

        totalDegree += canvasDegree;
        canvasDegree = 0;

        start.x = p[0];
        start.y = p[1];
        end.x = p[2];
        end.y = p[3];
        center.x = (start.x + end.x) / 2f;
        center.y = (start.y + end.y) / 2f;

        updateDetectRegion();
        update = true;
        invalidate();
    }

    private void scale(float dragX, float dragY, float dragX2, float dragY2, float x1, float y1, float x2, float y2) {
        float offsetX = (((x1 + x2) / 2f) - ((dragX + dragX2) / 2f));
        float offsetY = (((y1 + y2) / 2f) - ((dragY + dragY2) / 2f));
        afterMove(offsetX, offsetY);
        motionCenterP.set((x1 + x2) / 2f, (y1 + y2) / 2f);

        float oldDistance = PointF.length(dragX - dragX2, dragY - dragY2);
        float distance = PointF.length(x1 - x2, y1 - y2);
        float oldDiameter = PointF.length(end.x - start.x, end.y - start.y);
        float newDiameter = oldDiameter + (distance - oldDistance);
//        if (oldDistance > 0 && distance > 0) {
        if (oldDiameter > 0 && newDiameter > 0) {
            float size = PointF.length(start.x - end.x, start.y - end.y);
//            float scale = distance / oldDistance;
            float scale = newDiameter / oldDiameter;
            if (size <= minDiameter && scale <= 1f) {
                return;
            }
            if (size >= maxDiameter && scale >= 1f) {
                return;
            }
            if (scale == 1f) {
                return;
            }
            if (scale * size < minDiameter) {
                scale = minDiameter / size;
            } else if (scale * size > maxDiameter) {
                scale = maxDiameter / size;
            }
            // scaling at 100 percent
            realScaleRatio *= scale;
            if (realScaleRatio < 1.01f && realScaleRatio > 0.99f) {
                scale = 1f / scaleRatio;
            }
            scaleRatio *= scale;
            Matrix matrix = new Matrix();
            matrix.setScale(scale, scale, motionCenterP.x, motionCenterP.y);
            float[] floats = new float[]{start.x, start.y, end.x, end.y};
            matrix.mapPoints(floats);

            start.x = floats[0];
            start.y = floats[1];
            end.x = floats[2];
            end.y = floats[3];
            center.x = (start.x + end.x) / 2f;
            center.y = (start.y + end.y) / 2f;

            radius = PointF.length(start.x - end.x, start.y - end.y) / 2f;
            if (protractorMode == ProtractorMode.DEGREE180) {
                closeBtnRadius *= scale;
            }
            measurementRectStrokePaint.setStrokeWidth(measurementRectStrokePaint.getStrokeWidth() * scale);

        }
//            move(offsetX, offsetY);
        rotate(x1, dragX, y1, dragY, x2, dragX2, y2, dragY2);
        update = true;
        updateDetectLineRegion = true;
        invalidate();
    }

    private void drawProtractorLine(float x, float dragX, float y, float dragY) {
        PointF v1 = new PointF(dragX - center.x, dragY - center.y);
        PointF v2 = new PointF(x - center.x, y - center.y);
        float arc1 = (float) Math.atan2(v1.x, v1.y);
        float arc2 = (float) Math.atan2(v2.x, v2.y);
        float arc1D = ((float) Math.toDegrees(arc1) + 270) % 360;
        float arc2D = ((float) Math.toDegrees(arc2) + 270) % 360;
        float d = arc2D - arc1D;
        float degree = endAngle;

        if (protractorMode == ProtractorMode.DEGREE180) {
            degree += d;
            degree = (degree + 360) % 360;
            if (degree > 180 || degree < 0) {
                return;
            }
        } else {
            if (Math.abs(d) > 180) {
                d += d > 0 ? -360 : 360;
            }
            degree += d;
            if (degree - startAngle > 360) {
                degree = 360 + startAngle;
            } else if (degree - startAngle < -360) {
                degree = -360 + startAngle;
            }
        }

        endAngle = degree;
        update = true;
        invalidate();
    }

    private void switchMode() {
        int index = paintMode.ordinal();
        index = (index + 1) % ProtractorPaintMode.values().length;
        paintMode = ProtractorPaintMode.values()[index];

        update = true;
        invalidate();
    }

    @Override
    protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        if (visibility == VISIBLE) {
            update = true;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (reset) {
            reset = false;
            initProtractor();
            updateDetectRegion();
            update = true;
            if (listener != null) {
                listener.updateRecord(name);
            }
        }

        if (updateDetectLineRegion) {
            updateDetectLineRegion = false;
            updateDrawLinePath();
        }

        if (update) {
            update = false;

            canvas.save();

            if (translateX != 0 || translateY != 0) {
                canvas.translate(translateX, translateY);
            }

            if (canvasDegree != 0) {
                canvas.rotate(canvasDegree + getDegreeStopOffset(canvasDegree + totalDegree, ROTATE_SKIP, ROTATE_UNIT),
//                        center.x, center.y);
                        motionCenterP.x, motionCenterP.y);
            }

            RectF rectF = new RectF();
            Rect rect = new Rect();

            // draw ruler line
            canvas.drawPath(drawLinePath, drawLinePaint);

            // protractor
            float offsetX = (center.x - radius) - (int) (center.x - radius);
            float offsetY = (center.y - radius) - (int) (center.y - radius);
            if (protractorMode == ProtractorMode.DEGREE180) {
                rectF.set(center.x - radius, center.y - radius, center.x + radius, center.y + protractorBottomHeight);
            } else {
                rectF.set(center.x - radius, center.y - radius, center.x + radius, center.y + radius);
            }
            rectF.roundOut(rect);
            protractorDrawable.setBounds(rect);
            canvas.save();
            canvas.translate(offsetX, offsetY);
            canvas.rotate(totalDegree, center.x, center.y);
            protractorDrawable.draw(canvas);
            canvas.restore();

            // end ----- center ----- start
            final PointF v = getUnitVector(start.x - end.x, start.y - end.y);
            final PointF nv = getNormalVectorCCW(v);
            Matrix matrix = new Matrix();

            // close button
            float bx = center.x - v.x * closeBtnRadius;
            float by = center.y - v.y * closeBtnRadius;
            float[] mapPoints = new float[]{bx, by};
            final float btnHalfSize = buttonSize / 2f;
            if (protractorMode == ProtractorMode.DEGREE180) {
                matrix.setRotate(closeBtnDegree, center.x, center.y);
                matrix.mapPoints(mapPoints);
                bx = mapPoints[0];
                by = mapPoints[1];
            }
            closeBtnP.set(bx, by);
            offsetX = (closeBtnP.x - btnHalfSize) - (int) (closeBtnP.x - btnHalfSize);
            offsetY = (closeBtnP.y - btnHalfSize) - (int) (closeBtnP.y - btnHalfSize);
            rectF.set(closeBtnP.x - btnHalfSize, closeBtnP.y - btnHalfSize,
                    closeBtnP.x + btnHalfSize, closeBtnP.y + btnHalfSize);
            rectF.roundOut(rect);
            closeBtnDrawable.setBounds(rect);
            canvas.save();
            canvas.translate(offsetX, offsetY);
            canvas.rotate(totalDegree, closeBtnP.x, closeBtnP.y);
            closeBtnDrawable.draw(canvas);
            canvas.restore();

            // rotate button
            bx = center.x + v.x * closeBtnRadius;
            by = center.y + v.y * closeBtnRadius;
            mapPoints = new float[]{bx, by};
            if (protractorMode == ProtractorMode.DEGREE180) {
                matrix.setRotate(-closeBtnDegree, center.x, center.y);
                matrix.mapPoints(mapPoints);
                bx = mapPoints[0];
                by = mapPoints[1];
            }
            rotateBtnP.set(bx, by);
            rectF.set(rotateBtnP.x - btnHalfSize, rotateBtnP.y - btnHalfSize,
                    rotateBtnP.x + btnHalfSize, rotateBtnP.y + btnHalfSize);
            rectF.roundOut(rect);
            rotateBtnDrawable.setBounds(rect);
            canvas.save();
            canvas.rotate(totalDegree, rotateBtnP.x, rotateBtnP.y);
            rotateBtnDrawable.draw(canvas);
            canvas.restore();

            // draw distance/degree measurement
            if (protractorMode == ProtractorMode.DEGREE180) {
                bx = center.x - nv.x * (radius - measurementTopMargin * scaleRatio + measurementRectHeight / 2f);
                by = center.y - nv.y * (radius - measurementTopMargin * scaleRatio + measurementRectHeight / 2f);
            } else {
                bx = center.x - nv.x * closeBtnRadius;
                by = center.y - nv.y * closeBtnRadius;
            }
            canvas.save();
            canvas.rotate(totalDegree, bx, by);
            rectF.set(bx - measurementRectWidth / 2f, by - measurementRectHeight / 2f,
                    bx + measurementRectWidth / 2f, by + measurementRectHeight / 2f);
            canvas.drawRoundRect(rectF, measurementRectRadius, measurementRectRadius, measurementRectPaint);
            canvas.drawRoundRect(rectF, measurementRectRadius, measurementRectRadius, measurementRectStrokePaint);
            float tw = measurementRectTextPaint.measureText(measurementText);
            float th = TextLayoutUtil.getHeightOfString(measurementRectTextPaint, measurementText);
            canvas.drawText(measurementText, bx - tw / 2f, by + th / 2f, measurementRectTextPaint);
            canvas.restore();

            // draw mode button
            if (protractorMode == ProtractorMode.DEGREE180) {
                bx = bx + nv.x * (modeMargin + measurementRectHeight / 2f + btnHalfSize);
                by = by + nv.y * (modeMargin + measurementRectHeight / 2f + btnHalfSize);
            } else {
                bx = center.x + nv.x * closeBtnRadius;
                by = center.y + nv.y * closeBtnRadius;
            }
            modeP.set(bx, by);
            rectF.set(modeP.x - btnHalfSize, modeP.y - btnHalfSize, modeP.x + btnHalfSize, modeP.y + btnHalfSize);
            rectF.roundOut(rect);
            canvas.save();
            canvas.rotate(totalDegree, modeP.x, modeP.y);
            Drawable d = getModeButton(paintMode);
            d.setBounds(rect);
            d.draw(canvas);
            canvas.restore();

//            // debug
//            debugPaint.setColor(Color.RED);
//            canvas.drawPath(detectPath, debugPaint);
//            debugPaint.setColor(Color.BLUE);
//            canvas.drawPath(detectLinePath, debugPaint);

            canvas.restore();
        }
    }

    private Drawable getModeButton(ProtractorPaintMode mode) {
        switch (mode) {
            case ARC: {
                return arcModeDrawable;
            }
            case ANGLE: {
                return angleModeDrawable;
            }
            case LINE: {
                return lineModeDrawable;
            }
            case SOLID: {
                return solidModeDrawable;
            }
        }

        return arcModeDrawable;
    }

    private void initProtractor() {
        float width = getWidth();
        float height = getHeight();
        radius = defaultDiameter / 2f;

        if (protractorMode == ProtractorMode.DEGREE180) {
            center = new PointF(width / 2f, height / 2f + radius / 2f);
        } else {
            center = new PointF(width / 2f, height / 2f);
        }
        start = new PointF(center.x + radius, center.y);
        end = new PointF(center.x - radius, center.y);
    }

    private void updateDetectPath() {
        PointF v = getUnitVector(start.x - end.x, start.y - end.y);
        PointF nv = getNormalVectorCCW(v);
        detectPath.rewind();
        if (protractorMode == ProtractorMode.DEGREE180) {
            detectPath.moveTo(start.x, start.y);
            detectPath.addArc(center.x - radius, center.y - radius, center.x + radius, center.y + radius,
                    totalDegree, -180);
            detectPath.lineTo(end.x, end.y);
            detectPath.lineTo(end.x + nv.x * protractorBottomHeight, end.y + nv.y * protractorBottomHeight);
            detectPath.lineTo(start.x + nv.x * protractorBottomHeight, start.y + nv.y * protractorBottomHeight);
            detectPath.lineTo(start.x, start.y);
        } else {
            detectPath.addCircle(center.x, center.y, radius, Path.Direction.CCW);
        }

        // ruler detection region
        float w = drawLinePaint.getStrokeWidth() / 2f;
        float radius = PointF.length(start.x - center.x, start.y - center.y) + w;
        float interRadius = PointF.length(start.x - center.x, start.y - center.y) - w;
        PointF cStartV = getUnitVector(start.x - center.x, start.y - center.y);
        PointF cEndV = getUnitVector(end.x - center.x, end.y - center.y);
        PointF startPmoveV = new PointF(start.x + cStartV.x * w, start.y + cStartV.y * w);
        PointF startPmoveValt = new PointF(start.x - cStartV.x * w, start.y - cStartV.y * w);
        PointF endPmoveV = new PointF(end.x + cEndV.x * w, end.y + cEndV.y * w);
        PointF endPmoveValt = new PointF(end.x - cEndV.x * w, end.y - cEndV.y * w);

        detectLinePath.rewind();
        if (protractorMode == ProtractorMode.DEGREE180) {
            detectLinePath.moveTo(startPmoveV.x, startPmoveV.y);
            detectLinePath.addArc(center.x - radius, center.y - radius, center.x + radius, center.y + radius,
                    getStartDegree(start.x, start.y, center.x, center.y), -180);
            detectLinePath.lineTo(endPmoveV.x, endPmoveV.y);
            detectLinePath.lineTo(endPmoveValt.x, endPmoveValt.y);
            detectLinePath.addArc(center.x - interRadius, center.y - interRadius, center.x + interRadius, center.y + interRadius,
                    getStartDegree(end.x, end.y, center.x, center.y), 180);
            detectLinePath.lineTo(startPmoveValt.x, startPmoveValt.y);
            detectLinePath.lineTo(startPmoveV.x, startPmoveV.y);
            detectLinePath.addCircle(start.x, start.y, w, Path.Direction.CCW);
            detectLinePath.addCircle(end.x, end.y, w, Path.Direction.CCW);
        } else {
            detectLinePath.addCircle(center.x, center.y, radius, Path.Direction.CCW);
            detectLinePath.addCircle(center.x, center.y, interRadius, Path.Direction.CW);
        }
    }

    private float getStartDegree(float x, float y, float cx, float cy) {
        PointF v1 = new PointF(1, 0);
        PointF v2 = new PointF(x - cx, y - cy);
        float arc1 = (float) Math.atan2(v1.x, v1.y);
        float arc2 = (float) Math.atan2(v2.x, v2.y);
        return (float) Math.toDegrees(arc1 - arc2);
    }

    private void updateDrawLinePath() {
        drawLinePath.rewind();
        if (protractorMode == ProtractorMode.DEGREE180) {
            drawLinePath.addArc(center.x - radius, center.y - radius, center.x + radius, center.y + radius,
                    totalDegree, -180);
        } else {
            drawLinePath.addCircle(center.x, center.y, radius, Path.Direction.CCW);
        }
    }

    private void updateDetectRegion() {
        updateDetectPath();
        updateDrawLinePath();

        // ruler region
        Rect rect = new Rect();
        RectF rectF = new RectF();
        Region clipRegion = new Region();
        detectPath.computeBounds(rectF, true);
        rectF.roundOut(rect);
        clipRegion.set(rect);
        detectRegion.setPath(detectPath, clipRegion);

        // ruler line region
        detectLinePath.computeBounds(rectF, true);
        rectF.roundOut(rect);
        clipRegion.set(rect);
        detectLineRegion.setPath(detectLinePath, clipRegion);
    }

    private String getMeasurementText(MeasurementAction action) {
        if (action == MeasurementAction.DRAW) {
            float degree = endAngle - startAngle;
            degree += getDegreeStopOffset(degree, PROTRACTOR_SKIP, PROTRACTOR_UNIT);
            return String.format(Locale.ENGLISH, "%.1f°", protractorMode == ProtractorMode.DEGREE180 ? Math.abs(degree) : degree);
        } else if (action == MeasurementAction.ROTATE || canvasDegree != 0) {
            float degree = -(totalDegree + canvasDegree + getDegreeStopOffset(totalDegree + canvasDegree, ROTATE_SKIP, ROTATE_UNIT));
            float angle = (degree + 360) % 360;
            return String.format(Locale.ENGLISH, "%.1f°", angle);
        } else if (action == MeasurementAction.MULTI_FINGER) {
            return String.format(Locale.ENGLISH, "%d%%", Math.round(scaleRatio * 100f));
        }
        return "";
    }
}
